#include "mixer.hpp"

#include "wav.hpp"

#include <cmath>
#include "portaudio.h"
#include <vector>
#include <gvl/io/fstream.hpp>

Sound::Sound(gvl::octet_stream_reader& reader)
{
#if 0
	unsigned long middle = (unsigned long)(data.size() / 2);
	for(unsigned long i = 0; i < data.size(); ++i)
	{
		double ramp = i < middle ? i / double(middle) : (2*middle - i) / double(middle);
		int samp = int(std::sin(i * 6.28 * 440.0 / 44100) * 0.4 * 32768.0 * ramp);
		data[i] = samp;
	}
#endif

	{
		bool result = loadWav(reader, data);

		result = result;
	}
}

uint32_t Mixer::add(Sound* sound)
{
	if(freeChannels() > 1)
	{
		uint32_t chIdx = channels[writePos];

		Channel& ch = channelStates[chIdx];
		ch.sound = sound;
		ch.flags = Channel::Looping;
		ch.start = Pa_GetStreamTime(str) + outputLatency + 0.02; // TODO: Adjustable margin
		ch.soundpos = 0.0;
		ch.stride = 1.0;
		ch.volumes = 0x10001000;

		uint32_t id = ((nextId++) << 16) | chIdx;
		ch.id = id;

		GVL_WRITE_SYNC();
		writePos = (writePos + 1) & (channelCount-1);

		return id;
	}

	return 0;
}

bool Mixer::addChannel(int16_t* out, unsigned long frameCount, double now, Channel& ch)
{
	Sound* sound = ch.sound;
	double start = ch.start;

	int32_t diff = int32_t((start - now) * sampleRate);
	int32_t relbegin = diff;
	uint32_t soundlen = uint32_t(sound->data.size());
	//int32_t relend = relbegin + int32_t(soundlen);

	if(relbegin < 0)
		relbegin = 0;
	/*
	if(relend > int32_t(frameCount))
		relend = int32_t(frameCount);*/

	double scaler = (ch.volumes & 0xffff) * (1.0 / 0x1000);

	for(int32_t cur = relbegin; cur < frameCount; ++cur)
	{
		int32_t samp = out[cur];
		int32_t soundsamp = sound->data[ch.soundpos];
		samp += soundsamp * scaler;
		if(samp < -32768)
			samp = -32768;
		else if(samp > 32767)
			samp = 32767;
		out[cur] = int32_t(samp);
		ch.soundpos += ch.stride;
		if(ch.soundpos >= soundlen)
		{
			if(ch.flags & Channel::Looping)
				ch.soundpos = 0.0;
			else
				return false;
		}
	}

	return true;
}

void Mixer::mix(void *output, unsigned long frameCount, double now)
{
	int16_t* out = static_cast<int16_t*>(output);

	memset(out, 0, frameCount * sizeof(int16_t));

	uint32_t localWritePos = writePos;
	
	for(uint32_t c = tailPos; c != localWritePos; )
	{
		uint32_t chIdx = channels[c];
		Channel& ch = channelStates[chIdx];

		if(!addChannel(out, frameCount, now, ch))
		{
			swap(c, tailPos);
			tailPos = (tailPos + 1) & (channelCount-1);
		}

		c = (c + 1) & (channelCount-1);
	}

	baseFrame += frameCount;
}

Mixer mixer;

int callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
	mixer.mix(output, frameCount, timeInfo->outputBufferDacTime);

	return paContinue;
}

Mixer::Mixer()
: writePos(0), tailPos(0)
, baseFrame(0), nextId(0)
, initialized(false)
{
	for(uint32_t i = 0; i < channelCount; ++i)
		channels[i] = i;
}

void Mixer::start()
{
	if(initialized)
		return;

	Pa_Initialize();

	// TODO: Check for errors
	initialized = true;

	/*
	PaError err = Pa_OpenDefaultStream(&str, 0, 1, paInt16, 44100.0, 0,
                              callback, // streamCallback
                              0);
							  */
	PaStreamParameters hostApiOutputParameters;

    hostApiOutputParameters.device = Pa_GetDefaultOutputDevice();
    hostApiOutputParameters.channelCount = 1;
    hostApiOutputParameters.sampleFormat = paInt16;
    //hostApiOutputParameters.suggestedLatency = Pa_GetDeviceInfo( hostApiOutputParameters.device )->defaultLowOutputLatency;
	hostApiOutputParameters.suggestedLatency = 0.1;
    hostApiOutputParameters.hostApiSpecificStreamInfo = NULL;

    PaError err = Pa_OpenStream(
                 &str, 0, &hostApiOutputParameters,
                 44100.0, 0, paNoFlag, callback, 0);

	Pa_StartStream(str);

	PaStreamInfo const* info = Pa_GetStreamInfo(str);
	outputLatency = info->outputLatency;
	sampleRate = info->sampleRate;
}

Mixer::~Mixer()
{
	if(initialized)
		Pa_Terminate();
}